﻿using Sustainalytics.ClientDW.Entities;
using Sustainalytics.DataLayer;
using Sustainalytics.DataLayer.Mongo;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Sustainalytics.CompanySearch.StressTest
{
    /// <summary>
    /// Company search stress test core.
    /// </summary>
    public class CompanySearchStressTest
    {
        /// <summary>
        /// Main cancellation token source used for cancelling the entire test.
        /// </summary>
        CancellationTokenSource MainCancellationTokenSource { get; set; }

        /// <summary>
        /// Current test main task.
        /// </summary>
        Task TestTask { get; set; }

        /// <summary>
        /// Test specs used for running tests.
        /// </summary>
        IEnumerable<CompanySearchStressTestSpec> TestSpecs { get; set; }

        /// <summary>
        /// Company search repo.
        /// </summary>
        ICompanySearchRepository CompanySearchRepo { get; set; }

        /// <summary>
        /// Logger used to log stress test results.
        /// </summary>
        CompanySearchStressTestLogger Logger { get; set; }


        /// <summary>
        /// Ctor.
        /// </summary>
        /// <param name="testSpecs"> Test specs. </param>
        /// <param name="companySearchRepo"> Company search repo used to run the test against. </param>
        /// <param name="logger"> Logger used to log results. </param>
        public CompanySearchStressTest(
            IEnumerable<CompanySearchStressTestSpec> testSpecs, 
            ICompanySearchRepository companySearchRepo,
            CompanySearchStressTestLogger logger)
        {
            // create cancellation token
            this.MainCancellationTokenSource = new CancellationTokenSource();

            // set test specs
            this.TestSpecs = testSpecs;

            // company search repo to run the test against
            this.CompanySearchRepo = companySearchRepo;

            // logger used to log results
            this.Logger = logger;
        }


        /// <summary>
        /// Run stress test
        /// </summary>
        public Task RunAsync()
        {
            // initialize async
            this.TestTask = Task.Factory.StartNew(() =>
                {
                    // clean all previous results
                    this.Logger.Clean();

                    // execute each test
                    foreach (CompanySearchStressTestSpec testSpec in this.TestSpecs)
                    {
                        // check cancellation
                        this.MainCancellationTokenSource.Token.ThrowIfCancellationRequested();

                        // run test
                        this.RunTestSpec(testSpec);
                    }
                }, 
                this.MainCancellationTokenSource.Token);

            return this.TestTask;
        }

        /// <summary>
        /// Stop stress test.
        /// </summary>
        public Task Stop()
        {
            this.MainCancellationTokenSource.Cancel();

            return this.TestTask;
        }


        /// <summary>
        /// Run a specific test spec.
        /// </summary>
        /// <param name="testSpec"> Test spec. </param>
        private void RunTestSpec(CompanySearchStressTestSpec testSpec)
        {
            // execute multiple tests in parallel
            Parallel.For(
                // from
                fromInclusive: 0,
                // to
                toExclusive: testSpec.Count,
                // options
                parallelOptions: new ParallelOptions
                {
                    CancellationToken = this.MainCancellationTokenSource.Token,
                    MaxDegreeOfParallelism = testSpec.ConcurrentRequestsCount
                },
                // action
                body: (int index, ParallelLoopState loopState) =>
                {
                    try
                    {
                        // execute query
                        MongoExplain explain = this.CompanySearchRepo.SearchCompany_SingleQuery_Explain(testSpec.Term, testSpec.PagingOptions);

                        // log result
                        this.Logger.Log(testSpec, explain);
                    }
                    catch (Exception ex)
                    {
                        this.Logger.Log(testSpec, ex);
                    }
                });
        }
    }
}
